export type EventName = string | Symbol;

export type EventListener = (...args: any[]) => void;

export class EventEmitter {
  private eventMap = new Map<EventName, EventListener[]>();

  addEventListener(eventName: EventName, listener: EventListener) {
    if (this.eventMap.has(eventName)) {
      this.eventMap.get(eventName).push(listener);
    } else {
      this.eventMap.set(eventName, [listener]);
    }
  }

  emit (eventName: EventName, ...args: any[]) {
    if (this.eventMap.has(eventName)) {
      this.eventMap.get(eventName).forEach(listener => {
        try {
          listener(...args)
        } catch (err) {
          console.error(err);
          // TODO: emit a error event?
        }
      });
    }
  }

  removeEventListener(eventName: EventName, listener: EventListener) {
    if (this.eventMap.has(eventName)) {
      const listeners = this.eventMap.get(eventName);
      const index = listeners.findIndex(el => el === listener);
      if (index !== -1) {
        listeners.splice(index, 1);
      }
    }
  }

  off (eventName: EventName, listener: EventListener) {
    this.removeEventListener(eventName, listener);
  }

  on (eventName: EventName, listener: EventListener) {
    this.addEventListener(eventName, listener);
  }

  once (eventName: EventName, listener: EventListener) {
    const onceListener = (...args: any[]) => {
      this.removeEventListener(eventName, onceListener);
      listener(...args);
    };
    this.addEventListener(eventName, onceListener);
  }
}
